﻿#if UNITY_2019_4_OR_NEWER

using UnityEngine;
using System.Threading;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Devil
{
    [DefaultExecutionOrder(-1000), AddComponentMenu("")]
    public class ParallelUnity : MonoBehaviour
    {
        static readonly object sync_lock = new object();
        static RuntimePlatform sPlatform;
        public static RuntimePlatform platform { get { return sPlatform; } }

        static bool sDebugBuild;
        public static bool isDebugBuild { get { return sDebugBuild; } }
        static string sDeviceID;
        public static string deviceIdentifier { get { return sDeviceID; } }

        static bool sPlaying = true;
        public static bool isPlaying { get { return sPlaying; } }

        static string sPersistentDataPath;
        public static string persistentDataPath { get { return sPersistentDataPath; } }

        static string sStreamingPath;
        public static string streamingAssetsPath { get { return sStreamingPath; } }

        static string sDataPath;
        public static string dataPath { get { return sDataPath; } }

        static string sTempPath;
        public static string tempPath { get { return sTempPath; } }

        static float sTimeScale;
        static float sTime;
        static float sUnscaledTime;
        static int sFrames;
        public static float timeScale { get { return sTimeScale; } }
        public static float unscaledTime { get { return sUnscaledTime; } }
        public static float time { get { return sTime; } }
        public static int frameCount { get { return sFrames; } }

        static System.Random sRandom;
        public static float GetRandomValue()
        {
            lock (sync_lock)
            {
                if (sRandom == null)
                    sRandom = new System.Random(ParallelUtils.GenUniqueID());
            }
            return (float)sRandom.NextDouble();
        }

        public static int GetRandomValue(int min, int max)
        {
            lock (sync_lock)
            {
                if (sRandom == null)
                    sRandom = new System.Random(ParallelUtils.GenUniqueID());
            }
            return sRandom.Next(min, max);
        }

        public static float GetRandomValue(float min, float max)
        {
            lock (sync_lock)
            {
                if (sRandom == null)
                    sRandom = new System.Random(ParallelUtils.GenUniqueID());
            }
            var v = sRandom.NextDouble();
            return (max - min) * (float)v + min;
        }

        public static void Setup<T>(string instName) where T : ParallelUnity
        {
            if(sInstance == null)
            {
                if (string.IsNullOrEmpty(instName))
                    instName = string.Format("[{0}]", typeof(T).Name);
                var go = new GameObject(instName);
                DontDestroyOnLoad(go);
#if UNITY_EDITOR
                go.hideFlags |= HideFlags.DontSave | HideFlags.NotEditable;
#endif
                sInstance = go.AddComponent<T>();
                Initialize();
#if UNITY_EDITOR
                Debug.LogFormat("{0} was setup and named as \"{1}\"", typeof(T).Name, instName);
#endif
            }
        }

#if UNITY_EDITOR
        public static System.Action OnInstallized;

        [InitializeOnLoadMethod]
        static void InstallizeForEditor()
        {
            Initialize();
            EditorApplication.update += EditorUpdate;
        }

        static void EditorUpdate()
        {
            ParallelDispatcher.SetPause(EditorApplication.isPaused && EditorApplication.isPlaying);
            if (OnInstallized != null && ParallelDispatcher.GetInstance() != null)
            {
                OnInstallized();
                OnInstallized = null;
            }
            if (sInstance == null)
                ParallelDispatcher.MainUpdate(Time.deltaTime);
        }


        [MenuItem("Devil Framework/Print Parallel Cmds")]
#endif
        public static void PrintCmdInfo()
        {
            ThreadPool.QueueUserWorkItem((t) =>
            {
                var st = ParallelDispatcher.GetInstance();
                if (st != null)
                {
                    var str = st.GetCommandsInfo();
                    Debug.Log(str);
                }
            });
        }

        protected static ParallelUnity sInstance;

        static void Initialize()
        {
            sPlaying = Application.isPlaying;
            sPlatform = Application.platform;
            sDebugBuild = Debug.isDebugBuild;
            sDataPath = Application.dataPath;
            sStreamingPath = Application.streamingAssetsPath;
            sTimeScale = Time.timeScale;
            sTime = Time.time;
            sUnscaledTime = Time.unscaledTime;
            sFrames = Time.frameCount;
            sDeviceID = SystemInfo.deviceUniqueIdentifier;
            sTempPath = NativeFileSystem.Combine(Application.persistentDataPath, "temp");
#if UNITY_STANDALONE
            sPersistentDataPath = NativeFileSystem.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments),
                Application.companyName, Application.productName);
#else
            sPersistentDataPath = Application.persistentDataPath;
#endif
            ParallelDispatcher.InitApplicationData();
        }

        protected virtual void Awake()
        {
            if (sInstance == null)
                sInstance = this;
            Initialize();
        }

        protected virtual void OnEnable()
        {
            if (sInstance != null && sInstance != this)
            {
                enabled = false;
                throw new System.Exception("There are more than one ParallelMono in the scene.");
            }
            else
            {
                sInstance = this;
            }
        }

        protected virtual void OnDisable()
        {
            if(sInstance == this)
            {
                sInstance = null;
                ParallelDispatcher.QuitApplication();
            }
        }


        protected virtual void Update()
        {
            sTimeScale = Time.timeScale;
            sTime = Time.time;
            sUnscaledTime = Time.unscaledTime;
            sFrames = Time.frameCount;
            ParallelDispatcher.MainUpdate(Time.deltaTime);
        }

    }
}

#endif